package com.fary.validation;

import com.fary.beans.*;
import com.fary.core.MethodParameter;
import com.fary.core.SpringException;
import com.fary.core.convert.ConversionService;
import com.fary.core.convert.TypeDescriptor;
import com.fary.format.Formatter;
import com.fary.format.support.FormatterPropertyEditorAdapter;
import com.fary.util.Assert;
import com.fary.util.ObjectUtils;
import com.fary.util.PatternMatchUtils;
import com.fary.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.beans.PropertyEditor;
import java.lang.reflect.Field;
import java.net.BindException;
import java.util.*;

public class DataBinder implements PropertyEditorRegistry, TypeConverter {

    /** Default object name used for binding: "target". */
    public static final String DEFAULT_OBJECT_NAME = "target";

    /** Default limit for array and collection growing: 256. */
    public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;


    /**
     * We'll create a lot of DataBinder instances: Let's use a static logger.
     */
    protected static final Log logger = LogFactory.getLog(DataBinder.class);

    private final Object target;

    private final String objectName;

    // BindingResult：绑定错误、失败的时候会放进这里来~
    private AbstractPropertyBindingResult bindingResult;

    // 类型转换器，会注册最为常用的那么多类型转换Map<Class<?>, PropertyEditor> defaultEditors
    private SimpleTypeConverter typeConverter;

    // 默认忽略不能识别的字段~
    private boolean ignoreUnknownFields = true;

    // 不能忽略非法的字段（比如我要Integer，你给传aaa，那肯定就不让绑定了，抛错）
    private boolean ignoreInvalidFields = false;

    // 默认是支持级联的~~
    private boolean autoGrowNestedPaths = true;

    private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;

    // 这三个参数  都可以自己指定~~ 允许的字段、不允许的、必须的
    private String[] allowedFields;

    private String[] disallowedFields;

    private String[] requiredFields;

    // 转换器ConversionService
    private ConversionService conversionService;
    // 状态码处理器~
    private MessageCodesResolver messageCodesResolver;
    // 绑定出现错误的处理器~
    private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
    // 校验器（这个非常重要）
    private final List<Validator> validators = new ArrayList<>();


    /**
     * objectName没有指定，就用默认的
     */
    public DataBinder(Object target) {
        this(target, DEFAULT_OBJECT_NAME);
    }

    /**
     * Create a new DataBinder instance.
     */
    public DataBinder(Object target, String objectName) {
        this.target = ObjectUtils.unwrapOptional(target);
        this.objectName = objectName;
    }

    public Object getTarget() {
        return this.target;
    }

    public String getObjectName() {
        return this.objectName;
    }

    public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
        Assert.state(this.bindingResult == null, "DataBinder is already initialized - call setAutoGrowNestedPaths before other configuration methods");
        this.autoGrowNestedPaths = autoGrowNestedPaths;
    }

    public boolean isAutoGrowNestedPaths() {
        return this.autoGrowNestedPaths;
    }

    /**
     * 指定数组和集合自动增长的限制。 <p>默认为 256，防止出现大索引时出现 OutOfMemoryErrors。如果您的自动增长需求异常高，请提高此限制。
     */
    public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
        Assert.state(this.bindingResult == null,
                "DataBinder is already initialized - call setAutoGrowCollectionLimit before other configuration methods");
        this.autoGrowCollectionLimit = autoGrowCollectionLimit;
    }

    /**
     * Return the current limit for array and collection auto-growing.
     */
    public int getAutoGrowCollectionLimit() {
        return this.autoGrowCollectionLimit;
    }

    /**
     * Initialize standard JavaBean property access for this DataBinder.
     * <p>This is the default; an explicit call just leads to eager initialization.
     */
    public void initBeanPropertyAccess() {
        Assert.state(this.bindingResult == null, "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
        this.bindingResult = createBeanPropertyBindingResult();
    }

    protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
        BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());

        if (this.conversionService != null) {
            result.initConversion(this.conversionService);
        }
        if (this.messageCodesResolver != null) {
            result.setMessageCodesResolver(this.messageCodesResolver);
        }

        return result;
    }

    /**
     * Initialize direct field access for this DataBinder,
     * as alternative to the default bean property access.
     *
     * @see #initBeanPropertyAccess()
     * @see #createDirectFieldBindingResult()
     */
    public void initDirectFieldAccess() {
        Assert.state(this.bindingResult == null, "DataBinder is already initialized - call initDirectFieldAccess before other configuration methods");
        this.bindingResult = createDirectFieldBindingResult();
    }

    /**
     * Create the {@link AbstractPropertyBindingResult} instance using direct
     * field access.
     *
     * @since 4.2.1
     */
    protected AbstractPropertyBindingResult createDirectFieldBindingResult() {
        DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(),
                getObjectName(), isAutoGrowNestedPaths());

        if (this.conversionService != null) {
            result.initConversion(this.conversionService);
        }
        if (this.messageCodesResolver != null) {
            result.setMessageCodesResolver(this.messageCodesResolver);
        }

        return result;
    }

    /**
     * Return the internal BindingResult held by this DataBinder,
     * as an AbstractPropertyBindingResult.
     */
    protected AbstractPropertyBindingResult getInternalBindingResult() {
        if (this.bindingResult == null) {
            initBeanPropertyAccess();
        }
        return this.bindingResult;
    }

    /**
     * Return the underlying PropertyAccessor of this binder's BindingResult.
     */
    protected ConfigurablePropertyAccessor getPropertyAccessor() {
        return getInternalBindingResult().getPropertyAccessor();
    }

    /**
     * Return this binder's underlying SimpleTypeConverter.
     */
    protected SimpleTypeConverter getSimpleTypeConverter() {
        if (this.typeConverter == null) {
            this.typeConverter = new SimpleTypeConverter();
            if (this.conversionService != null) {
                this.typeConverter.setConversionService(this.conversionService);
            }
        }
        return this.typeConverter;
    }

    /**
     * Return the underlying TypeConverter of this binder's BindingResult.
     */
    protected PropertyEditorRegistry getPropertyEditorRegistry() {
        if (getTarget() != null) {
            return getInternalBindingResult().getPropertyAccessor();
        } else {
            return getSimpleTypeConverter();
        }
    }

    /**
     * Return the underlying TypeConverter of this binder's BindingResult.
     */
    protected TypeConverter getTypeConverter() {
        if (getTarget() != null) {
            return getInternalBindingResult().getPropertyAccessor();
        } else {
            return getSimpleTypeConverter();
        }
    }

    /**
     * 返回此 DataBinder 创建的 BindingResult 实例。这允许在绑定操作之后方便地访问绑定结果。
     */
    public BindingResult getBindingResult() {
        return getInternalBindingResult();
    }

    public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
        this.ignoreUnknownFields = ignoreUnknownFields;
    }

    /**
     * Return whether to ignore unknown fields when binding.
     */
    public boolean isIgnoreUnknownFields() {
        return this.ignoreUnknownFields;
    }

    public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
        this.ignoreInvalidFields = ignoreInvalidFields;
    }

    /**
     * Return whether to ignore invalid fields when binding.
     */
    public boolean isIgnoreInvalidFields() {
        return this.ignoreInvalidFields;
    }

    /**
     * 设置指定的可以绑定的字段，默认是所有字段~~~
     * 例如，在绑定HTTP请求参数时，限制这一点以避免恶意用户进行不必要的修改。
     * 简单的说：我可以控制只有指定的一些属性才允许你修改~~~~
     * 注意：它支持xxx*,*xxx,*xxx*这样的通配符  支持[]这样子来写~
     */
    public void setAllowedFields(String... allowedFields) {
        this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);
    }

    /**
     * 注册每个绑定进程所必须的字段。
     */
    public String[] getAllowedFields() {
        return this.allowedFields;
    }

    public void setDisallowedFields(String... disallowedFields) {
        this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
    }

    public String[] getDisallowedFields() {
        return this.disallowedFields;
    }

    public void setRequiredFields(String... requiredFields) {
        this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);
        if (logger.isDebugEnabled()) {
            logger.debug("DataBinder requires binding of required fields [" + StringUtils.arrayToCommaDelimitedString(requiredFields) + "]");
        }
    }

    public String[] getRequiredFields() {
        return this.requiredFields;
    }

    public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
        Assert.state(this.messageCodesResolver == null, "DataBinder is already initialized with MessageCodesResolver");
        this.messageCodesResolver = messageCodesResolver;
        if (this.bindingResult != null && messageCodesResolver != null) {
            this.bindingResult.setMessageCodesResolver(messageCodesResolver);
        }
    }

    public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
        Assert.notNull(bindingErrorProcessor, "BindingErrorProcessor must not be null");
        this.bindingErrorProcessor = bindingErrorProcessor;
    }

    /**
     * Return the strategy for processing binding errors.
     */
    public BindingErrorProcessor getBindingErrorProcessor() {
        return this.bindingErrorProcessor;
    }

    /**
     * Set the Validator to apply after each binding step.
     *
     * @see #addValidators(Validator...)
     * @see #replaceValidators(Validator...)
     */
    public void setValidator(Validator validator) {
        assertValidators(validator);
        this.validators.clear();
        if (validator != null) {
            this.validators.add(validator);
        }
    }

    private void assertValidators(Validator... validators) {
        Object target = getTarget();
        for (Validator validator : validators) {
            if (validator != null && (target != null && !validator.supports(target.getClass()))) {
                throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + target);
            }
        }
    }

    /**
     * Add Validators to apply after each binding step.
     *
     * @see #setValidator(Validator)
     * @see #replaceValidators(Validator...)
     */
    public void addValidators(Validator... validators) {
        assertValidators(validators);
        this.validators.addAll(Arrays.asList(validators));
    }

    /**
     * Replace the Validators to apply after each binding step.
     *
     * @see #setValidator(Validator)
     * @see #addValidators(Validator...)
     */
    public void replaceValidators(Validator... validators) {
        assertValidators(validators);
        this.validators.clear();
        this.validators.addAll(Arrays.asList(validators));
    }

    /**
     * Return the primary Validator to apply after each binding step, if any.
     */
    public Validator getValidator() {
        return (!this.validators.isEmpty() ? this.validators.get(0) : null);
    }

    /**
     * Return the Validators to apply after data binding.
     */
    public List<Validator> getValidators() {
        return Collections.unmodifiableList(this.validators);
    }


    //---------------------------------------------------------------------
    // Implementation of PropertyEditorRegistry/TypeConverter interface
    //---------------------------------------------------------------------

    /**
     * Specify a Spring 3.0 ConversionService to use for converting
     * property values, as an alternative to JavaBeans PropertyEditors.
     */
    public void setConversionService(ConversionService conversionService) {
        Assert.state(this.conversionService == null, "DataBinder is already initialized with ConversionService");
        this.conversionService = conversionService;
        if (this.bindingResult != null && conversionService != null) {
            this.bindingResult.initConversion(conversionService);
        }
    }

    /**
     * Return the associated ConversionService, if any.
     */
    public ConversionService getConversionService() {
        return this.conversionService;
    }

    /**
     * Add a custom formatter, applying it to all fields matching the
     * {@link Formatter}-declared type.
     * <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
     *
     * @param formatter the formatter to add, generically declared for a specific type
     * @see #registerCustomEditor(Class, PropertyEditor)
     * @since 4.2
     */
    public void addCustomFormatter(Formatter<?> formatter) {
        FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
        getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
    }

    /**
     * Add a custom formatter for the field type specified in {@link Formatter} class,
     * applying it to the specified fields only, if any, or otherwise to all fields.
     * <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
     *
     * @param formatter the formatter to add, generically declared for a specific type
     * @param fields    the fields to apply the formatter to, or none if to be applied to all
     * @see #registerCustomEditor(Class, String, PropertyEditor)
     * @since 4.2
     */
    public void addCustomFormatter(Formatter<?> formatter, String... fields) {
        FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
        Class<?> fieldType = adapter.getFieldType();
        if (ObjectUtils.isEmpty(fields)) {
            getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
        } else {
            for (String field : fields) {
                getPropertyEditorRegistry().registerCustomEditor(fieldType, field, adapter);
            }
        }
    }

    /**
     * Add a custom formatter, applying it to the specified field types only, if any,
     * or otherwise to all fields matching the {@link Formatter}-declared type.
     * <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
     *
     * @param formatter  the formatter to add (does not need to generically declare a
     *                   field type if field types are explicitly specified as parameters)
     * @param fieldTypes the field types to apply the formatter to, or none if to be
     *                   derived from the given {@link Formatter} implementation class
     * @see #registerCustomEditor(Class, PropertyEditor)
     * @since 4.2
     */
    public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes) {
        FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
        if (ObjectUtils.isEmpty(fieldTypes)) {
            getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
        } else {
            for (Class<?> fieldType : fieldTypes) {
                getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
            }
        }
    }

    @Override
    public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
        getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);
    }

    @Override
    public void registerCustomEditor(Class<?> requiredType, String field, PropertyEditor propertyEditor) {
        getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor);
    }

    @Override
    public PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath) {
        return getPropertyEditorRegistry().findCustomEditor(requiredType, propertyPath);
    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws SpringException {
        return getTypeConverter().convertIfNecessary(value, requiredType);
    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam) throws SpringException {
        return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType, Field field) throws SpringException {
        return getTypeConverter().convertIfNecessary(value, requiredType, field);
    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType, TypeDescriptor typeDescriptor) throws SpringException {
        return getTypeConverter().convertIfNecessary(value, requiredType, typeDescriptor);
    }


    /**
     * 绑定键值对
     */
    public void bind(PropertyValues pvs) {
        MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
        doBind(mpvs);
    }

    protected void doBind(MutablePropertyValues mpvs) {
        // 校验
        checkAllowedFields(mpvs);
        checkRequiredFields(mpvs);
        // 绑定
        applyPropertyValues(mpvs);
    }

    /**
     * 根据允许的字段检查给定的属性值，删除不允许的字段的值。
     */
    protected void checkAllowedFields(MutablePropertyValues mpvs) {
        PropertyValue[] pvs = mpvs.getPropertyValues();
        for (PropertyValue pv : pvs) {
            String field = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
            if (!isAllowed(field)) {
                mpvs.removePropertyValue(pv);
                getBindingResult().recordSuppressedField(field);
                if (logger.isDebugEnabled()) {
                    logger.debug("Field [" + field + "] has been removed from PropertyValues " +
                            "and will not be bound, because it has not been found in the list of allowed fields");
                }
            }
        }
    }

    protected boolean isAllowed(String field) {
        // 允许的字段
        String[] allowed = getAllowedFields();
        // 不允许的字段
        String[] disallowed = getDisallowedFields();
        return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
                (ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
    }

    /**
     * 根据必填字段检查给定的属性值，在适当的情况下生成缺失字段错误。
     */
    protected void checkRequiredFields(MutablePropertyValues mpvs) {
        String[] requiredFields = getRequiredFields();
        if (!ObjectUtils.isEmpty(requiredFields)) {
            Map<String, PropertyValue> propertyValues = new HashMap<>();
            PropertyValue[] pvs = mpvs.getPropertyValues();
            for (PropertyValue pv : pvs) {
                String canonicalName = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
                propertyValues.put(canonicalName, pv);
            }
            for (String field : requiredFields) {
                PropertyValue pv = propertyValues.get(field);
                boolean empty = (pv == null || pv.getValue() == null);
                if (!empty) {
                    if (pv.getValue() instanceof String) {
                        empty = !StringUtils.hasText((String) pv.getValue());
                    } else if (pv.getValue() instanceof String[]) {
                        String[] values = (String[]) pv.getValue();
                        empty = (values.length == 0 || !StringUtils.hasText(values[0]));
                    }
                }
                if (empty) {
                    // 使用绑定错误处理器创建 FieldError。
                    getBindingErrorProcessor().processMissingFieldError(field, getInternalBindingResult());
                    // Remove property from property values to bind:
                    // It has already caused a field error with a rejected value.
                    if (pv != null) {
                        mpvs.removePropertyValue(pv);
                        propertyValues.remove(field);
                    }
                }
            }
        }
    }

    protected void applyPropertyValues(MutablePropertyValues mpvs) {
        try {
            getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
        } catch (SpringException ex) {
            getBindingErrorProcessor().processPropertyAccessException(ex, getInternalBindingResult());
        }
    }


    /**
     * Invoke the specified Validators, if any.
     *
     * @see #setValidator(Validator)
     * @see #getBindingResult()
     */
    public void validate() {
        Object target = getTarget();
        Assert.state(target != null, "No target to validate");
        BindingResult bindingResult = getBindingResult();
        // Call each validator with the same binding result
        for (Validator validator : getValidators()) {
            validator.validate(target, bindingResult);
        }
    }

    /**
     * Invoke the specified Validators, if any, with the given validation hints.
     * <p>Note: Validation hints may get ignored by the actual target Validator.
     *
     * @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
     * @see #setValidator(Validator)
     * @see SmartValidator#validate(Object, Errors, Object...)
     * @since 3.1
     */
    public void validate(Object... validationHints) {
        Object target = getTarget();
        Assert.state(target != null, "No target to validate");
        BindingResult bindingResult = getBindingResult();
        // Call each validator with the same binding result
        for (Validator validator : getValidators()) {
            if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                ((SmartValidator) validator).validate(target, bindingResult, validationHints);
            } else if (validator != null) {
                validator.validate(target, bindingResult);
            }
        }
    }

    /**
     * Close this DataBinder, which may result in throwing
     * a BindException if it encountered any errors.
     *
     * @return the model Map, containing target object and Errors instance
     * @throws BindException if there were any errors in the bind operation
     * @see BindingResult#getModel()
     */
    public Map<?, ?> close() throws BindException {
        if (getBindingResult().hasErrors()) {
            throw new BindException(getBindingResult());
        }
        return getBindingResult().getModel();
    }

}